#include <stdlib.h>
#include <stdio.h>
#include <math.h>

typedef struct {
	unsigned char R;
	unsigned char G;
	unsigned char B;
} pixel_t;

int printBMP(char* file, pixel_t* FrameBuffer, int x_size, int y_size){
	FILE *fpBMP;
        
	int i, j;

	// Header and 3 bytes per pixel
	unsigned long ulBitmapSize = (y_size * x_size * 3)+54; 
	char ucaBitmapSize[4];

	ucaBitmapSize[3]= (ulBitmapSize & 0xFF000000) >> 24;
	ucaBitmapSize[2]= (ulBitmapSize & 0x00FF0000) >> 16;
	ucaBitmapSize[1]= (ulBitmapSize & 0x0000FF00) >> 8;
	ucaBitmapSize[0]= (ulBitmapSize & 0x000000FF);

	/* Create bitmap file */
	fpBMP=fopen(file,"wb");
	if(fpBMP == 0)
		return 0;

	/* Write header */
	/* All values are in big endian order (LSB first) */

	// BMP signature + filesize
	fprintf(fpBMP,"%c%c%c%c%c%c%c%c%c%c", 66, 77, ucaBitmapSize[0], 
			ucaBitmapSize[1], ucaBitmapSize[2], ucaBitmapSize[3], 0, 0, 
			0, 0); 

	// Image offset, infoheader size, image width
	fprintf(fpBMP,"%c%c%c%c%c%c%c%c%c%c", 54, 0, 0, 0, 40, 0 , 0, 0, 
			(x_size & 0x00FF), (x_size & 0xFF00)>>8); 

	// Image height, number of panels, num bits per pixel
	fprintf(fpBMP,"%c%c%c%c%c%c%c%c%c%c", 0, 0, (y_size & 0x00FF),
			(y_size & 0xFF00) >> 8, 0, 0, 1, 0, 24, 0); 

	// Compression type 0, Size of image in bytes 0 because uncompressed
	fprintf(fpBMP,"%c%c%c%c%c%c%c%c%c%c", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); 
	fprintf(fpBMP,"%c%c%c%c%c%c%c%c%c%c", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
	fprintf(fpBMP,"%c%c%c%c", 0, 0 ,0, 0);

	for(i = y_size - 1; i >= 0; i--) {
		/* in bitmaps the bottom line of the image is at the
		   beginning of the file */
		for(j = 0; j < x_size; j++){
			putc((FrameBuffer[i * x_size + j].B), fpBMP);
			putc((FrameBuffer[i * x_size + j].G), fpBMP);
			putc((FrameBuffer[i * x_size + j].R), fpBMP);
		}
		for(j = 0; j < x_size % 4; j++)
			putc(0, fpBMP);
	}

	fclose(fpBMP);
	return 1;
}

int LoadBitmapFile(char *filename, pixel_t* bitmapImage, int x_size, int y_size) {

  FILE *filePtr; //our file pointer
  int i,j;

  //open filename in read binary mode
  filePtr = fopen(filename,"rb");
  if (filePtr == NULL)
    return 0;

  //move file point to the begging of bitmap data
  fseek(filePtr, 54, SEEK_SET);

  
  for(i = y_size - 1; i >= 0; i--) {
    for(j = 0; j < x_size; j++){
      bitmapImage[i * x_size + j].B = (unsigned char) getc(filePtr);
      bitmapImage[i * x_size + j].G = (unsigned char) getc(filePtr);
      bitmapImage[i * x_size + j].R = (unsigned char) getc(filePtr);
    }
  }

  //close file and return bitmap image data
  fclose(filePtr);
  return 1;
}

double Normal[] =  
{ 
	0, 0, 0, 
	0, 1, 0, 
	0, 0, 0 
};

double Blur[] =
{
	0.0, 0.2,  0.0,
	0.2, 0.2,  0.2,
	0.0, 0.2,  0.0
};

double Emboss[] =
{
	-1, -1,  0,
	-1,  0,  1,
	 0,  1,  1
};

double Sharpen[] =
{
	-1, -1, -1,
	-1,  9, -1,
	-1, -1, -1
};

double Edge[] =
{
	 0, -1,  0,
	-1,  4, -1,
	 0, -1,  0
};

#define max(a,b) ((a)>(b))?(a):(b)

#define min(a,b) ((a)>(b))?(b):(a)

int main(int argc, char**argv){
	int imageWidth = 240;
	int imageHeight = 320;
	int i;
	int x, y;
	int filterX, filterY;
	int filterWidth = 3;
	int filterHeight = 3;

	pixel_t* image = (pixel_t*) malloc(imageWidth * imageHeight * sizeof(pixel_t));
	pixel_t* FrameBuffer = (pixel_t*) malloc(imageWidth * imageHeight * sizeof(pixel_t));
	double* edgeBuffer = (double*) malloc(imageWidth * imageHeight * sizeof(double));

	//load the image
	i = !LoadBitmapFile(argv[1], image, imageWidth, imageHeight);

	for(x = 0; x < imageWidth; x++) 
		for(y = 0; y < imageHeight; y++) { 
			double red = 0.0, green = 0.0, blue = 0.0; 

			for(filterX = 0; filterX < filterWidth; filterX++) 
				for(filterY = 0; filterY < filterHeight; filterY++) { 
					int imageX = (x - filterWidth / 2 + filterX + imageWidth) % imageWidth; 
					int imageY = y - filterHeight / 2 + filterY; 
					imageY = max(imageY,0);
					imageY = min(imageY,(imageHeight-1));
					red   += image[imageX + imageY * imageWidth].R * Edge[filterX + filterY*filterWidth]; 
					green += image[imageX + imageY * imageWidth].G * Edge[filterX + filterY*filterWidth]; 
					blue  += image[imageX + imageY * imageWidth].B * Edge[filterX + filterY*filterWidth]; 
				}

			edgeBuffer[x + y * imageWidth] = ((red + green + blue)/3)/255;
			if(edgeBuffer[x + y * imageWidth] > 0.08) {
				edgeBuffer[x + y * imageWidth] = 1;
			}else{
				edgeBuffer[x + y * imageWidth] = 0;
			}
		}

	/* apply the filter */
	for(x = 0; x < imageWidth; x++) 
		for(y = 0; y < imageHeight; y++) { 
			double red = 0.0, green = 0.0, blue = 0.0; 
			double val = 0.0; 
			double valr = 0.0; 
			double valg = 0.0; 
			double valb = 0.0; 
			valr = sqrt(pow((x - imageWidth/2),2) + pow((y - imageHeight/2),2));
			valg = sqrt(pow((x - 50 - imageWidth/2),2) + pow((y + 75 - imageHeight/2),2));
			valb = sqrt(pow((x + 50 - imageWidth/2),2) + pow((y + 75 - imageHeight/2),2));
			val = sqrt(pow((x - imageWidth/2),2) + pow((y - imageHeight/2),2));
			valr = valr/255;//
			valg = valg/255;//
			valb = valb/255;//
			val = val/255;

			if(edgeBuffer[x + y * imageWidth] == 0){
				//multiply every value of the filter with corresponding image pixel 
				for(filterX = 0; filterX < filterWidth; filterX++) 
					for(filterY = 0; filterY < filterHeight; filterY++) { 
						int imageX = (x - filterWidth / 2 + filterX + imageWidth) % imageWidth; 
						int imageY = y - filterHeight / 2 + filterY; 
						imageY = max(imageY,0);
						imageY = min(imageY,(imageHeight-1));
						red   += image[imageX + imageY * imageWidth].R * Blur[filterX + filterY*filterWidth]; 
						green += image[imageX + imageY * imageWidth].G * Blur[filterX + filterY*filterWidth]; 
						blue  += image[imageX + imageY * imageWidth].B * Blur[filterX + filterY*filterWidth]; 
					} 

				red   = (image[x + y * imageWidth].R*(1-val) + val*red  );
				green = (image[x + y * imageWidth].G*(1-val) + val*green);
				blue  = (image[x + y * imageWidth].B*(1-val) + val*blue );
			}else{
				red   = image[x + y * imageWidth].R;
				green = image[x + y * imageWidth].G;
				blue  = image[x + y * imageWidth].B;
			}

			//truncate values smaller than zero and larger than 255
			FrameBuffer[x + y * imageWidth].R = (unsigned char) min(max((int)((1.3+valr*0.3) * red + 0), 0), 255); 
			FrameBuffer[x + y * imageWidth].G = (unsigned char) min(max((int)((1.0+valg*0.3) * green + 0), 0), 255); 
			FrameBuffer[x + y * imageWidth].B = (unsigned char) min(max((int)((0.7+valb*0.3) * blue + 0), 0), 255);

		}    

	for(x = 0; x < imageWidth; x++) 
		for(y = 0; y < imageHeight; y++) { 
			double val = 0.0; 
			val = 355 - sqrt(pow((x - imageWidth/2),2) + pow((y - imageHeight/2),2));
			val /= 255;

			FrameBuffer[x + y * imageWidth].R = (unsigned char) min(max((int)(val*FrameBuffer[x + y * imageWidth].R), 0), 255); 
			FrameBuffer[x + y * imageWidth].G = (unsigned char) min(max((int)(val*FrameBuffer[x + y * imageWidth].G), 0), 255); 
			FrameBuffer[x + y * imageWidth].B = (unsigned char) min(max((int)(val*FrameBuffer[x + y * imageWidth].B), 0), 255);
		}

	// Store the output image
	i = !printBMP(argv[2],FrameBuffer,imageWidth,imageHeight);


	free(image);
	free(FrameBuffer);
	free(edgeBuffer);
	return i;
}
